package com.clt.ess.gjzw;
import java.math.BigInteger;

public class SM3Digest {
    private static final int BYTE_LENGTH = 32;
    private static final int BLOCK_LENGTH = 64;
    private static final int BUFFER_LENGTH = 64*2;
    private byte[] xBuf = new byte[BUFFER_LENGTH];
    private int xBufOff;
    private byte[] V = SM3.iv;
    private int cntBlock = 0;
    public SM3Digest() {
    }

    public int doFinal(byte[] out, int outOff) {
        byte[] tmp = doFinal();
        System.arraycopy(tmp, 0, out, 0, tmp.length);
        return BYTE_LENGTH;
    }

    public String getAlgorithmName() {
        return "SM3";
    }

    public int getDigestSize() {
        return BYTE_LENGTH;
    }

    public void reset() {
        xBufOff = 0;
        cntBlock = 0;
        V = SM3.iv;
    }


    public void update(byte[] in, int inOff, int len) {
        if(xBufOff+len > BUFFER_LENGTH) {
            int tmpLen = xBufOff+len-BUFFER_LENGTH;
            System.arraycopy(in, inOff, xBuf, xBufOff, BUFFER_LENGTH-xBufOff);
            doUpdate();
            xBufOff = 0;
            int i=1;
            while(tmpLen > BUFFER_LENGTH) {
                tmpLen -= BUFFER_LENGTH;
                System.arraycopy(in, inOff+BUFFER_LENGTH*i, xBuf, xBufOff, BUFFER_LENGTH-xBufOff);
                doUpdate();
                xBufOff = 0;
                i++;
            }
            System.arraycopy(in, inOff+len-tmpLen, xBuf, xBufOff, tmpLen);
            xBufOff += tmpLen;

        } else if(xBufOff+len == BUFFER_LENGTH) {
            System.arraycopy(in, inOff, xBuf, xBufOff, len);
            doUpdate();
            xBufOff = 0;
        } else {
            System.arraycopy(in, inOff, xBuf, xBufOff, len);
            xBufOff += len;
        }
    }

    private void doUpdate() {
        byte[] B = new byte[BLOCK_LENGTH];
        for(int i=0; i<BUFFER_LENGTH; i +=BLOCK_LENGTH) {
            System.arraycopy(xBuf, i, B, 0, B.length);
            doHash(B);
        }
        cntBlock += BUFFER_LENGTH/BLOCK_LENGTH;
    }

    private void doHash(byte[] B) {
        V = SM3.CF(V, B);
    }

    private byte[] doFinal() {
        byte[] B = new byte[BLOCK_LENGTH];
        byte[] buffer = new byte[xBufOff];
        System.arraycopy(xBuf, 0, buffer, 0, buffer.length);
        byte[] tmp = SM3.padding(buffer, cntBlock);
        for(int i=0; i<tmp.length; i +=BLOCK_LENGTH) {
            System.arraycopy(tmp, i, B, 0, B.length);
            doHash(B);
            cntBlock++;
        }

        return V;
    }

    private byte[] getSM2Za(byte[] x, byte[] y, byte[] id) {
        byte[] tmp = Util.IntToByte(id.length*8);
        byte[] buffer = new byte[32*6+2+id.length];
        buffer[0] = tmp[1];
        buffer[1] = tmp[0];
        byte[] a = Util.getA();
        byte[] b = Util.getB();
        byte[] gx = Util.getGx();
        byte[] gy = Util.getGy();
        int dPos = 2;
        System.arraycopy(id, 0, buffer, dPos, id.length);
        dPos += id.length;
        System.arraycopy(a, 0, buffer, dPos, 32);
        dPos += 32;
        System.arraycopy(b, 0, buffer, dPos, 32);
        dPos += 32;
        System.arraycopy(gx, 0, buffer, dPos, 32);
        dPos += 32;
        System.out.println(dPos);
        System.arraycopy(gy, 0, buffer, dPos, 32);
        dPos += 32;
        System.arraycopy(x, 0, buffer, dPos, 32);
        dPos += 32;
        System.arraycopy(y, 0, buffer, dPos, 32);
        dPos += 32;

        //PrintUtil.printWithHex(buffer);
        SM3Digest digest = new SM3Digest();
        digest.update(buffer, 0, buffer.length);
        byte[] out = new byte[32];
        digest.doFinal(out, 0);

        return out;
    }


    public void addId(BigInteger affineX, BigInteger affineY, byte[] id) {
        byte[] x = Util.asUnsigned32ByteArray(affineX);
        byte[] y = Util.asUnsigned32ByteArray(affineY);
        byte[] tmp = getSM2Za(x, y, id);
        reset();
        System.arraycopy(tmp, 0, xBuf, xBufOff, 32);
        xBufOff = 32;
    }
}

class SM3 {
    public static final byte[] iv = new BigInteger("7380166f4914b2b9172442d7da8a0600a96f30bc163138aae38dee4db0fb0e4e", 16).toByteArray();
    public static int[] Tj = new int[64];
    static {
        for(int i=0; i<16; i++) {
            Tj[i] = 0x79cc4519;
        }
        for(int i=16; i<64; i++) {
            Tj[i] = 0x7a879d8a;
        }
    }

    public static byte[] CF(byte[] V, byte[] B) {
        int[] v, b;
        v = convert(V);
        b = convert(B);

        return convert(CF(v, b));
    }

    private static int[] convert(byte[] arr) {
        int[] out = new int[arr.length/4];
        byte[] tmp = new byte[4];
        for(int i=0; i<arr.length; i += 4) {
            System.arraycopy(arr, i, tmp, 0, 4);
            out[i/4] = bigEndianByteToInt(tmp);
        }

        return out;
    }

    private static byte[] convert(int[] arr) {
        byte[] out = new byte[arr.length*4];
        byte[] tmp = null;
        for(int i=0; i<arr.length; i++) {
            tmp = bigEndianIntToByte(arr[i]);
            System.arraycopy(tmp, 0, out, i*4, 4);
        }

        return out;
    }

    public static int[] CF(int[] V, int[] B) {
        int a, b, c, d, e, f, g, h;
        int ss1, ss2, tt1, tt2;
        a = V[0];
        b = V[1];
        c = V[2];
        d = V[3];
        e = V[4];
        f = V[5];
        g = V[6];
        h = V[7];
        /*System.out.print("  ");
        System.out.print(Integer.toHexString(a)+" ");
        System.out.print(Integer.toHexString(b)+" ");
        System.out.print(Integer.toHexString(c)+" ");
        System.out.print(Integer.toHexString(d)+" ");
        System.out.print(Integer.toHexString(e)+" ");
        System.out.print(Integer.toHexString(f)+" ");
        System.out.print(Integer.toHexString(g)+" ");
        System.out.print(Integer.toHexString(h)+" ");
        System.out.println();*/

        int[][] arr = expand(B);
        int[] w = arr[0];
        int[] w1 = arr[1];
        /*System.out.println("W");
        print(w);
        System.out.println("W1");
        print(w1);*/
        for(int j=0; j<64; j++) {
            ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(Tj[j], j));
            ss1 = bitCycleLeft(ss1, 7);
            ss2 = ss1 ^ bitCycleLeft(a, 12);
            tt1 = FFj(a, b, c, j) + d + ss2 + w1[j];
            tt2 = GGj(e, f, g, j) + h + ss1 + w[j];
            d = c;
            c = bitCycleLeft(b, 9);
            b = a;
            a = tt1;
            h = g;
            g = bitCycleLeft(f, 19);
            f = e;
            e = P0(tt2);

            /*System.out.print(j+" ");
            System.out.print(Integer.toHexString(a)+" ");
            System.out.print(Integer.toHexString(b)+" ");
            System.out.print(Integer.toHexString(c)+" ");
            System.out.print(Integer.toHexString(d)+" ");
            System.out.print(Integer.toHexString(e)+" ");
            System.out.print(Integer.toHexString(f)+" ");
            System.out.print(Integer.toHexString(g)+" ");
            System.out.print(Integer.toHexString(h)+" ");
            System.out.println();*/
        }
        //System.out.println("*****************************************");

        int[] out = new int[8];
        out[0] = a ^ V[0];
        out[1] = b ^ V[1];
        out[2] = c ^ V[2];
        out[3] = d ^ V[3];
        out[4] = e ^ V[4];
        out[5] = f ^ V[5];
        out[6] = g ^ V[6];
        out[7] = h ^ V[7];

        return out;
    }

    private static int[][] expand(byte[] B) {
        int W[] = new int[68];
        int W1[] = new int[64];
        byte[] tmp = new byte[4];
        for(int i=0; i<B.length; i+=4) {
            for(int j=0; j<4; j++) {
                tmp[j] = B[i+j];
            }
            W[i/4] = bigEndianByteToInt(tmp);
        }

        for(int i=16; i<68; i++) {
            W[i] = P1(W[i-16] ^ W[i-9] ^ bitCycleLeft(W[i-3], 15)) ^ bitCycleLeft(W[i-13], 7) ^ W[i-6];
        }

        for(int i=0; i<64; i++) {
            W1[i] = W[i] ^ W[i+4];
        }

        int arr[][] = new int[][]{W, W1};

        return arr;
    }

    private static int[][] expand(int[] B) {
        return expand(convert(B));
    }

    private static byte[] bigEndianIntToByte(int num) {
        return back(Util.IntToByte(num));
    }

    private static int bigEndianByteToInt(byte[] bytes) {
        return Util.ByteToInt(back(bytes));
    }

    private static int FFj(int X, int Y, int Z, int j) {
        if(j>=0 && j<=15) {
            return FF1j(X, Y, Z);
        } else {
            return FF2j(X, Y, Z);
        }
    }
    private static int GGj(int X, int Y, int Z, int j) {
        if(j>=0 && j<=15) {
            return GG1j(X, Y, Z);
        } else {
            return GG2j(X, Y, Z);
        }
    }
    /***********************************************/
    private static int FF1j(int X, int Y, int Z) {
        int tmp = X ^ Y ^ Z;

        return tmp;
    }

    private static int FF2j(int X, int Y, int Z) {
        int tmp = ( (X & Y) | (X & Z) | (Y & Z) );

        return tmp;
    }

    private static int GG1j(int X, int Y, int Z) {
        int tmp = X ^ Y ^ Z;

        return tmp;
    }

    private static int GG2j(int X, int Y, int Z) {
        int tmp = (X & Y) | (~X & Z) ;

        return tmp;
    }

    private static int P0(int X) {
        int t = X ^ bitCycleLeft(X, 9) ^ bitCycleLeft(X, 17);

        return t;
    }

    private static int P1(int X) {
        int t = X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23);

        return t;
    }

    public static byte[] padding(byte[] in, int bLen) {
        int k = 448 - (8 * in.length+1) % 512;
        if( k < 0) {
            k = 960 - (8 * in.length+1) % 512;
        }
        k += 1;
        byte[] padd = new byte[k/8];
        padd[0] = (byte)0x80;
        long n = in.length * 8+bLen*512;
        byte[] out = new byte[in.length+k/8+64/8];
        int pos = 0;
        System.arraycopy(in, 0, out, 0, in.length);
        pos += in.length;
        System.arraycopy(padd, 0, out, pos, padd.length);
        pos += padd.length;
        byte[] tmp = back(Util.LongToByte(n));
        System.arraycopy(tmp, 0, out, pos, tmp.length);

        return out;
    }

    private static byte[] back(byte[] in) {
        byte[] out = new byte[in.length];
        for(int i=0; i<out.length; i++) {
            out[i] = in[out.length-i-1];
        }

        return out;
    }

    private static int bitCycleLeft(int n, int bitLen) {
        bitLen %= 32;
        byte[] tmp = bigEndianIntToByte(n);
        int byteLen = bitLen / 8;
        int len = bitLen % 8;
        if( byteLen > 0) {
            tmp = byteCycleLeft(tmp, byteLen);
        }

        if(len > 0) {
            tmp = bitSmall8CycleLeft(tmp, len);
        }

        return bigEndianByteToInt(tmp);
    }

    private static byte[] bitSmall8CycleLeft(byte[] in, int len) {
        byte[] tmp = new byte[in.length];
        int t1, t2, t3;
        for(int i=0; i<tmp.length; i++) {
            t1 = (byte) ((in[i] & 0x000000ff)<<len);
            t2 = (byte) ((in[(i+1)%tmp.length] & 0x000000ff)>>(8-len));
            t3 = (byte) (t1 | t2);
            tmp[i] = (byte)t3;
        }


        return tmp;
    }

    private static byte[] byteCycleLeft(byte[] in, int byteLen) {
        byte[] tmp = new byte[in.length];
        System.arraycopy(in, byteLen, tmp, 0, in.length-byteLen);
        System.arraycopy(in, 0, tmp, in.length-byteLen, byteLen);

        return tmp;
    }

    public static void print(int[] arr) {
        for(int i=0; i<arr.length; i++) {
            /*System.out.print(PrintUtil.toHexString(back(ConvertUtil.IntToByte(arr[i]))) + " ");
            if((i+1) % 8 == 0) {
                System.out.println();
            }*/
            System.out.print(Integer.toHexString(arr[i]) + " ");
            if((i+1)%8 == 0) {
                System.out.println();
            }
        }
        System.out.println();
    }
}

class Util {
    private static BigInteger p = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
    private static BigInteger a = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
    private static BigInteger b = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
    private static BigInteger n = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
    private static BigInteger Gx = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    private static BigInteger Gy = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    private static byte[] getP() {
        return asUnsigned32ByteArray(p);
    }
    public static byte[] getA() {
        return asUnsigned32ByteArray(a);
    }
    public static byte[] getB() {
        return asUnsigned32ByteArray(b);
    }
    public static byte[] getN() {
        return asUnsigned32ByteArray(n);
    }
    public static byte[] getGx() {
        return asUnsigned32ByteArray(Gx);
    }
    public static byte[] getGy() {
        return asUnsigned32ByteArray(Gy);
    }
    /*static {
        System.out.println("p len = " + p.toByteArray().length);
        System.out.println("a len = " + a.toByteArray().length);
        System.out.println("b len = " + b.toByteArray().length);
        System.out.println("n len = " + n.toByteArray().length);
        System.out.println("Gx len = " + Gx.toByteArray().length);
        System.out.println("Gy len = " + Gy.toByteArray().length);
    }*/
    public static byte[] IntToByte(int num) {
        byte[] bytes = new byte[4];

        bytes[0] = (byte)(0xff&(num>>0));
        bytes[1] = (byte)(0xff&(num>>8));
        bytes[2] = (byte)(0xff&(num>>16));
        bytes[3] = (byte)(0xff&(num>>24));

        return bytes;
    }

    public static int ByteToInt(byte[] bytes) {
        int num = 0;
        int temp;
        temp = (0x000000ff & (bytes[0]))<<0;
        num = num | temp;
        temp = (0x000000ff & (bytes[1]))<<8;
        num = num | temp;
        temp = (0x000000ff & (bytes[2]))<<16;
        num = num | temp;
        temp = (0x000000ff & (bytes[3]))<<24;
        num = num | temp;

        return num;
    }

    public static byte[] LongToByte(long num) {
        byte[] bytes = new byte[8];

        for(int i=0; i<8; i++) {
            bytes[i] = (byte)(0xff&(num>>(i*8)));
        }

        return bytes;
    }


    public static byte[] asUnsigned32ByteArray(BigInteger n) {
        return asUnsignedNByteArray(n, 32);
    }
    public static byte[] asUnsignedNByteArray(BigInteger x, int length) {
        if(x == null) {
            return null;
        }

        byte[] tmp = new byte[length];
        int len = x.toByteArray().length;
        if(len > length+1) {
            return null;
        }

        if(len == length+1) {
            if(x.toByteArray()[0] != 0) {
                return null;
            } else {
                System.arraycopy(x.toByteArray(), 1, tmp, 0, length);
                return tmp;
            }
        } else {
            System.arraycopy(x.toByteArray(), 0, tmp, length-len, len);
            return tmp;
        }

    }
}